<?php

class process {
	public $pid = -1;
	public $user_id;
	public $cmd = '';
	public $cmd_full = '';
	public $state = 0;
	public $file_out;
	public $file_err;
	public $time_start;
	public $time_end;
	public $time_run = 0;
	public $group;
	public $owner_class;
	public $owner_function;
	public $result = '';
	public $tag;
}
class wapi_proc extends wapi_mod_base {
	public $cnt = 0;
	function _construct(){
		if ($this->firstrun){
			$this->db_create();
		}
	}
	function db_create(){
		global $wapi;
		$sql = "CREATE TABLE proc (
		pid INTEGER PRIMARY KEY NOT NULL,
		user_id TEXT NOT NULL,
		owner_class TEXT NOT NULL,
		owner_function TEXT NOT NULL,
		cmd TEXT NOT NULL,
		cmd_full TEXT NOT NULL,
		state INTEGER NOT NULL,
		file_out TEXT NOT NULL,
		file_err TEXT NOT NULL,
		time_start TEXT NOT NULL,
		time_modified TEXT,
		time_end TEXT,
		time_run INTEGER,
		keep_log INTEGER NOT NULL,
		result TEXT,
		tag TEXT
		)";
		$wapi->db->exec($sql);
	}
	function run($cmd, $wait = -1, $tag ='',$no_log=false) {
		global $wapi;
		$backtrace = debug_backtrace();
   		$p = new process();
   		$waited = 0;
   		$p->cmd = $cmd;
   		$p->user_id = $wapi->user->user_id;
   		$p->file_out = $this->get_tmpfile('WOUT');
   		$p->file_err = $this->get_tmpfile('WERR');
//		//$p->cmd_full = $cmd.' > '.$p->file_out.' 2> '.$p->file_err.' < /dev/null & echo $!';
//		$p->cmd_full = 'nohup sh -c "'.$cmd.' 1> '.$p->file_out.' 2> '.$p->file_err.' < /dev/null" & echo $!';
//		$p->cmd_full = "nohup $cmd 1> $p->file_out 2> $p->file_err < /dev/null & echo " . '$!';
		if ($no_log){
			$keep_log = 0;
		} else {
			$keep_log = 1;
		}
		// Below tested to work with sleep command and multiple commands seperated by semi-colons
		if ($this->cmd_exists('nohup')) {
			$nohup = 'nohup ';
		} else {
			$nohup = '';
		}
		$p->cmd_full = $nohup . 'sh -c "' . $cmd . '" 1> '.$p->file_out.' 2> '.$p->file_err.' < /dev/null & echo $!';
		$p->time_start = date("Y-m-d H:i:s",time());
		$p->state = 1;
		$p->owner_class = $backtrace[1]['class'];
		$p->owner_function = $backtrace[1]['function'];
		$p->tag = $tag;
		$p->pid = trim(shell_exec($p->cmd_full));
		if ($p->pid > 0) {
			// Save to proc table
			$sql = "INSERT OR REPLACE INTO proc (pid,user_id,cmd,cmd_full,state,file_out,file_err,time_start,owner_class,owner_function,tag,keep_log)
			VALUES ($p->pid,'" . $p->user_id . "','" . $wapi->db->esc($cmd) . "','" . $wapi->db->esc($p->cmd_full) . "',
			$p->state,'" . $wapi->db->esc($p->file_out) . "','" . $wapi->db->esc($p->file_err) . "',
			'" . $wapi->db->esc($p->time_start) . "','" . $wapi->db->esc($p->owner_class) . "',
			'" . $wapi->db->esc($p->owner_function) . "','" . $wapi->db->esc($p->tag) . "',$keep_log)";
			$ret = $wapi->db->exec($sql);
			// - Save to proc table
			if ($wait >= 0) {
				while($this->pid_running($p->pid)) {
					sleep(1);
					$n = date("Y-m-d H:i:s",time());
					$waited = $this->time_diff($p->time_start,$n);
					if ($wait > 0 && $waited >= $wait) {
						break;
					}
				}
				$this->poll();
			}
		}
		return $p;
	}
	function poll(){
		// Load all runnning processes from db
		global $wapi;
		$cnt = 0;
		$sql = 'SELECT * FROM proc WHERE state = 1';
		// - Load all runnning processes from db
		if ($wapi->db->query($sql)){
			$res = $wapi->db->results;
			foreach ($res as $r) {
				$result = '';
				$time_end = '';
				$time_run = '';
				$done_time = '';
				$running = $this->pid_running($r['pid']);
				if ($running) {
					$cnt ++;
					$state = 1;
				} else {
					$state = 0;
				}
				if (is_file($r['file_err'])){
					if (filesize($r['file_err']) != 0) {
						$done_time = filemtime($r['file_err']);
						$result = file_get_contents($r['file_err']);
						if (!$running)
							$state = -1;
					}
					if (!$running)
						unlink($r['file_err']);
				}
				if (is_file($r['file_out'])){
					if (filesize($r['file_out']) != 0) {
						$done_time = filemtime($r['file_out']);
						$result = file_get_contents($r['file_out']);
						if (!$running)
							$state = 0;
					}
					if (!$running)
						unlink($r['file_out']);
				}
				if ($done_time == ''){
					$time_end = $r['time_start'];
				} else {
					$time_end = date("Y-m-d H:i:s",$done_time);
				}
				if ($r['keep_log'] && $running){
					// save process info
					$time_run = $this->time_diff($r['time_start'],$time_end);
					$sql = "UPDATE proc SET
					state = $state,
					result = '" . $wapi->db->esc($result) . "',
					time_modified = '" . $wapi->db->esc($time_end) . "',
					time_run = $time_run
					WHERE
					pid = " . $r['pid'] . " AND state = 1";
					$ret = $wapi->db->exec($sql);
				} else if (!$r['keep_log'] && $running){
					// save everything except $result
					$time_run = $this->time_diff($r['time_start'],$time_end);
					$sql = "UPDATE proc SET
					state = $state,
					time_modified = '" . $wapi->db->esc($time_end) . "',
					time_run = $time_run
					WHERE
					pid = " . $r['pid'] . " AND state = 1";
					$ret = $wapi->db->exec($sql);
				} else if ($r['keep_log'] && !$running){
					// save final results for process
					$time_run = $this->time_diff($r['time_start'],$time_end);
					$sql = "UPDATE proc SET
					state = $state,
					result = '" . $wapi->db->esc($result) . "',
					time_end = '" . $wapi->db->esc($time_end) . "',
					time_run = $time_run
					WHERE
					pid = " . $r['pid'] . " AND state = 1";
					$ret = $wapi->db->exec($sql);
				} else if (!$r['keep_log'] && !$running){
					// log info no longer needed. discard record
					$sql = "DELETE FROM proc WHERE pid = " . $r['pid'] . " AND state = 1";
					$ret = $wapi->db->exec($sql);
				}
			}
		}
		$this->cnt = $cnt;
		return $cnt;
	}
	function pid_running($pid){
		exec("ps $pid", $pstate);
		return(count($pstate) >= 2);
   	}
	function get_tmpfile($prefix = ''){
		$fname = tempnam(sys_get_temp_dir(), $prefix);
		return $fname;
	}
	function time_diff($firstTime,$lastTime){
		$firstTime = strtotime($firstTime);
		$lastTime = strtotime($lastTime);
		$timeDiff = $lastTime - $firstTime;
		return $timeDiff;
	}
	function show_processes($show_all = false, $clear_done = false){
		echo "Process count: " . $this->run_cnt . " (" . count($this->proc) . ")<br />\n";
		if (count($this->proc) > 0) {
			foreach ($this->proc as $i => $p){
				if ($p->state == 1 || $show_all) {
					$this->show_proc($i);
				}
			}
		}
		if ($clear_done) {
			$this->clear_done();
		}
	}
	function show_proc($i){
		$p = $this->proc[$i];
		echo "Index: " . $i . "<br />\n";
		echo "PID: " . $p->pid . "<br />\n";
		echo "Cmd: " . $p->cmd . "<br />\n";
		echo "Full Cmd: " . $p->full_cmd . "<br />\n";
		echo "State: " . $p->state . "<br />\n";
		echo "Data Cnt: " . strlen($p->result) . "<br />\n";
		echo "Err Cnt: " . strlen($p->err) . "<br />\n";
		echo "Start: " . $p->time_start . "<br />\n";
		echo "End: " . $p->time_end . "<br />\n";
		echo "Runtime: " . $p->runtime . "<br />\n";
		echo "<br />";
	}
	function get_proc($pid,$field){
		global $wapi;
		$sql = "SELECT $field FROM proc WHERE pid = $pid";
		if ($wapi->db->query($sql)) {
			$res = $wapi->db->results;
			$r = $res[0];
			return $r[$field];
		}
		return null;
	}
	function set_proc($pid,$field,$value){
		global $wapi;
		if (is_string($value)){
			$value = $wapi->db->esc($value);
			$sql = "UPDATE proc SET $field = '$value' WHERE pid = $pid";
		} else {
			$sql = "UPDATE proc SET $field = $value WHERE pid = $pid";
		}
		$ret = $wapi->db->exec($sql);
	}
	public function cmd_exists($cmd){
		// command -v  >/dev/null && echo $PATH || echo "-1"
		$res = trim(shell_exec('command -v '.$cmd.' >/dev/null && echo $PATH || echo "-1"'));
		if ($res == '-1') {
			return '';
		} else {
			return $res;
		}
	}
}
?>